// @ts-nocheck
import { isObject } from "./type"

/**
 * Recursively check if the objects match.
 */
function _containsMatch(array, pattern) {
  let i = -1
  const length = array.length
  while (++i < length) {
    if (deepMatches(array[i], pattern)) {
      return true
    }
  }

  return false
}

function _matchArray(target, pattern) {
  let i = -1
  const patternLength = pattern.length
  while (++i < patternLength) {
    if (!_containsMatch(target, pattern[i])) {
      return false
    }
  }

  return true
}

function _matchObject(target, pattern) {
  let result = true
  for (const key in pattern) {
    if (pattern.hasOwnProperty(key)) {
      if (!deepMatches(target[key], pattern[key])) {
        // Return false to break out of forOwn early
        return (result = false)
      }
    }
  }
  return result
}

export function deepMatches(target, pattern) {
  if (target && typeof target === 'object') {
    if (isArray(target) && isArray(pattern)) {
      return _matchArray(target, pattern)
    } else {
      return _matchObject(target, pattern)
    }
  } else {
    return target === pattern
  }
}


/**
 * Recursively checks for same properties and values.
 */
/**
 * Converts argument into a valid iterator.
 * Used internally on most array/object/collection methods that receives a
 * callback/iterator providing a shortcut syntax.
 */
function _makeIterator(src, thisObj) {
  if (src == null) {
    return val => val
  }
  switch (typeof src) {
    case 'function':
      // function is the first to improve perf (most common case)
      // also avoid using `Function#call` if not needed, which boosts
      // perf a lot in some cases
      return (typeof thisObj !== 'undefined') ? function (val, i, arr) {
        return src.call(thisObj, val, i, arr)
      } : src
    case 'object':
      return function (val) {
        return deepMatches(val, src)
      }
    case 'string':
    case 'number':
      return obj => obj[src]
  }
}

function _every(obj, callback, thisObj) {
  callback = _makeIterator(callback, thisObj)
  let result = true
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      if (!callback(obj[key], key, obj)) {
        result = false
        break
      }
    }
  }
  return result
}

// Makes a function to compare the object values from the specified compare operation callback.
function _makeCompare(callback) {
  return function (value, key) {
    return this.hasOwnProperty(key) && callback(value, this[key])
  }
}

function _checkProperties(value, key) {
  return this.hasOwnProperty(key)
}

export function equal(a: any, b: any, callback = (a: any, b: any) => a === b) {
  if (!isObject(a) || !isObject(b)) {
    return callback(a, b)
  }

  return (_every(a, _makeCompare(callback), b) && _every(b, _checkProperties, a))
}

export function deepEqual(a, b, callback = (a, b) => a === b) {
  if (!isObject(a) || !isObject(b)) {
    return callback(a, b)
  }

  function compare(a, b) {
    return deepEqual(a, b, callback)
  }

  return equal(a, b, compare)
}
